package com.michaelbulava.TFS.vcs.revision;

import com.intellij.openapi.diff.ApplicationStarterBase;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Ref;
import com.intellij.openapi.vcs.FilePath;
import com.intellij.openapi.vcs.VcsException;
import com.intellij.openapi.vcs.changes.ContentRevision;
import com.intellij.openapi.vcs.history.VcsRevisionNumber;
import com.intellij.openapi.vfs.VirtualFile;
import com.michaelbulava.TFS.vcs.TFSVcs;
import com.michaelbulava.TFS.vcs.exceptions.TFSException;
import com.michaelbulava.TFS.vcs.exceptions.UnableToCreateTFSFilePathException;
import com.michaelbulava.TFS.vcs.internal.TFSFiles.TFSFilePath;
import com.michaelbulava.TFS.vcs.utils.TFSFileUtil;
import com.microsoft.tfs.core.clients.versioncontrol.soapextensions.DeletedState;
import com.microsoft.tfs.core.clients.versioncontrol.soapextensions.Item;
import com.microsoft.tfs.core.clients.versioncontrol.soapextensions.ItemType;
import com.microsoft.tfs.core.clients.versioncontrol.soapextensions.Workspace;
import com.microsoft.tfs.core.clients.versioncontrol.specs.DownloadSpec;
import com.microsoft.tfs.core.clients.versioncontrol.specs.version.ChangesetVersionSpec;
import com.microsoft.tfs.util.Check;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.io.IOException;
import java.io.OutputStream;
import java.text.MessageFormat;

/**
 * Created by MichaelA on 8/5/2015.
 */
public final class TFSContentRevision implements ContentRevision {
    private final Project project;
    protected final Workspace workspace;
    protected int itemId = Integer.MIN_VALUE;
    protected final int changeSet;
    protected Item myItem;
    protected FilePath myFile;

    //TODO this needs some work to handle the connection being disconnected.
    @Nullable
    private byte[] myContent;



    public static TFSContentRevision create(Project project,  Workspace workspace,
                                            FilePath localFile, int changeSet,
                                            int itemId) {
        Check.notNull(workspace, "workspace");
        Check.notNull(localFile, "localFile");

        return new TFSContentRevision(project, workspace, changeSet, itemId, localFile);
        //return new ContentRevision1(project, workspace, itemId, changeSet, localFile);
    }

    public static TFSContentRevision create(Project project, Workspace workspace,
                                            int changeSet, int itemId) throws TFSException {
        Check.notNull(workspace, "workspace");

//        Item item = workspace.getClient().getItem(itemId, changeSet);
//        FilePath localPath;
//        if (item != null){
//            String localFilePath = workspace.getMappedLocalPath(item.getServerItem());
//            if (localFilePath == null || localFilePath.length() == 0)
//                throw new TFSException("No mapping found for " + item.getServerItem());
//            localPath = TFSFileUtil.getFilePath(localFilePath, item.getItemType() == ItemType.FOLDER);
//        } else {
//            localPath = null;
//        }

        return new TFSContentRevision(project, workspace, itemId, changeSet);
//        return new ContentRevision1(project, workspace, itemId, changeSet,
//                localPath, item);
    }

    public static TFSContentRevision create(Project project, FilePath localPath,
                                            int changeSet) throws TFSException{
        Check.notNull(localPath, "localPath");

        Module fileMod  = TFSFileUtil.getModuleForFile(localPath, project);
        if (fileMod != null){
            Workspace workspace = TFSVcs.getWorkspaceForModule(fileMod);
            if (!(localPath instanceof TFSFilePath))
                try {
                    localPath = new TFSFilePath(workspace, localPath);
                } catch (UnableToCreateTFSFilePathException e) {
                }
            return new TFSContentRevision(project, workspace, changeSet, localPath);
        }
        return null;
    }


    protected TFSContentRevision(Project project, Workspace workspace, int itemId,
                                 int changeSet){
        this(project, workspace, changeSet, itemId, null);
//        this.project = project;
//        this.workspace = workspace;
        Check.isTrue(itemId != Integer.MAX_VALUE, "valid ItemId must be supplied");
//        this.itemId = itemId;
//        this.changeSet = changeSet;
    }


    private TFSContentRevision(Project project, Workspace workspace, int changeset, FilePath file) {
        this(project, workspace, changeset, Integer.MIN_VALUE, file);
//        this.project = project;
//        this.workspace = workspace;
//        this.changeSet = changeset;
//        this.myFile = file;
    }

    private TFSContentRevision(Project project, Workspace workspace, int changeSet, int itemId, FilePath file){
        this.project = project;
        this.workspace = workspace;
        this.changeSet = changeSet;
        this.itemId = itemId;
        this.myFile = file;
    }

    protected final Item getItem(){
        if (this.myItem == null){
            if (itemId != Integer.MIN_VALUE)
                myItem = workspace.getClient().getItem(itemId, changeSet, true);
            else
                myItem = workspace.getClient().getItem(getFile().getPath(), new ChangesetVersionSpec(getChangeSet()),
                        DeletedState.ANY, true);
        }
        return myItem;
    }

    protected final int getItemId(){
        if (itemId == Integer.MIN_VALUE)
            itemId = getItem().getItemID();
        return this.itemId;
    }

    protected int getChangeSet(){
        return this.changeSet;
    }

    @Override
    public final String getContent() throws VcsException{
        return new String(doGetContent(), getFile().getCharset(this.project));
    }

    @NotNull
    @Override
    public FilePath getFile() {
        if(myFile  == null)
            myFile = TFSFileUtil.getFilePath(getItem(), workspace);

        return myFile;
    }

    @NotNull
    @Override
    public VcsRevisionNumber getRevisionNumber() {
        return new VcsRevisionNumber.Int(changeSet);
    }

    public byte[] doGetContent() throws VcsException{
        if (this.myContent == null){
            try{
                this.myContent = loadContent();
            } catch (TFSException e) {
                throw new VcsException(e);
            } catch (IOException e) {
                throw new VcsException(e);
            }
        }
        return myContent;
    }

    private byte[] loadContent() throws TFSException, IOException{
        TFSContentStore store = TFSContentStoreFactory.find(workspace.getServerURI(), getItemId(), getChangeSet());
        if (store == null){
            Item item = getItem();
            if (item == null)
                return null;
            if (item.getItemType() == ItemType.FOLDER)
                throw new ApplicationStarterBase.OperationFailedException(MessageFormat.format(
                        "''{0}'' refers to a folder", new Object[]{getFile().getPresentableUrl()}));

            final String downloadUrl = item.getDownloadURL();
            TFSVcs.assertTrue(downloadUrl != null, "Item without download URL: " + item.getServerItem());

            store = TFSContentStoreFactory.create(workspace.getServerURI(), itemId, changeSet);
            final Ref<TFSException> exceptionRef = new Ref<>();
            store.saveContent(new TFSFileUtil.ContentWriter() {
                @Override
                public void write(OutputStream outputStream) {
                    try{
                        DownloadSpec spec = new DownloadSpec(downloadUrl);
                        workspace.getClient().downloadFileToStream(spec, outputStream, true);
                    } catch(Throwable e){
                        exceptionRef.set(new TFSException(e));
                    }
                }
            });
            if (!exceptionRef.isNull())
                throw exceptionRef.get();
        }
        return store.loadContent();
    }


    @Override
    public String toString(){
        return MessageFormat.format("TFSContentRevision [file={0}, ChangeSet={1}, revision={2}",
                new Object[] { getFile(), getChangeSet(), getRevisionNumber().asString() });
    }


//    private static class ContentRevision1 extends TFSContentRevision{
//
//        private FilePath localFile;
//
//        private ContentRevision1(Project project, Workspace workspace, int itemId,
//                                 int changeSet, FilePath localFile){
//            this(project, workspace, itemId, changeSet, localFile, null);
//        }
//
//        private ContentRevision1(Project project, Workspace workspace, int itemId,
//                                 int changeSet, FilePath localFile, Item item){
//            super(project, workspace, itemId, changeSet);
//            this.localFile = localFile;
//            this.myItem = item;
//        }
//
//        private ContentRevision1(Project project, Workspace workspace, FilePath localFile, int changeset){
//            super(project, workspace, changeset)
//        }
//
//        @NotNull
//        @Override
//        public FilePath getFile() {
//            return localFile;
//        }
//
//
//    }
}
